#include "header.h"
#include <errno.h>

#define MAX_EVENT_SIZE 100
#define EPOLL_TIMEOUT 5000

int main()
{
    // 1. 创建监听的套接字
    int listenfd = socket(AF_INET , SOCK_STREAM , 0);
    ERROR_CHECK(listenfd , -1 , "socket");

    printf(">>> 监听套接字的描述符：%d \n" , listenfd);

    // 设置套接字属性：网络地址可以重用
    int on = 1;
    setsockopt(listenfd , SOL_SOCKET , SO_REUSEADDR , &on , sizeof(on));

    struct sockaddr_in serveraddr;
    memset(&serveraddr , 0 , sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(9090);
    serveraddr.sin_addr.s_addr = inet_addr("127.0.0.1");

    // 2.绑定网络地址
    int ret = bind(listenfd , (const struct sockaddr *)&serveraddr , sizeof(serveraddr));
    ERROR_CHECK(ret , -1 , "bind");

    // 3.监听新连接的到来
    ret = listen(listenfd , 1);
    ERROR_CHECK(ret , -1 , "listen");

    // 4.创建 epoll 实例
    int epfd = epoll_create1(0);
    ERROR_CHECK(epfd , -1 , "epoll_create1");
    printf("epoll实例的文件描述符：%d \n" , epfd);

    // 5.epoll 添加对 listenfd 事件的监听
    struct epoll_event ev;
    memset(&ev , 0 , sizeof(ev));
    ev.data.fd = listenfd;
    ev.events = EPOLLIN; // 对 listenfd 上的读时间进行监听
    ret = epoll_ctl(epfd , EPOLL_CTL_ADD , listenfd , &ev);
    ERROR_CHECK(ret , -1 , "epoll_ctl");

    struct epoll_event evtArr[MAX_EVENT_SIZE];
    // 6.事件循环，不断的对事件进行监听
    while(1)
    {
        int nready = epoll_wait(epfd , evtArr , MAX_EVENT_SIZE , EPOLL_TIMEOUT);
        if(nready == -1 && errno == EINTR)
        {
            continue; // 被信号影响
        }
        else if (nready == -1)
        {
            perror("epoll_wait");
        }
        else if (nready == 0)
        {
            printf("epoll 超时 \n");
        }
        else 
        {
            printf(">>> 准备就绪的文件描述符个数：%d \n" , epfd);
            
            // nready 大于 0 的情况，有文件描述符就绪
            for(int i = 0 ; i < nready ; ++i)
            {
                // 遍历数组获取就绪的文件描述符
                int fd = evtArr[i].data.fd;

                if(fd == listenfd)
                {
                    // 6.1 有新连接进来
                    struct sockaddr_in clientaddr;
                    socklen_t len = sizeof(clientaddr);
                    int netfd = accept(listenfd , 
                                       (struct sockaddr *)&clientaddr ,
                                       &len);
                    ERROR_CHECK(netfd , -1 , "accept");

                    // 6.2 新连接信息的打印
                    printf("新的文件描述符：%d\n" , netfd);
                    printf("服务端：%s:%d --->客户端：%s:%d 已连接！\n" ,
                           inet_ntoa(serveraddr.sin_addr) , 
                           ntohs(serveraddr.sin_port) , 
                           inet_ntoa(clientaddr.sin_addr) , 
                           ntohs(clientaddr.sin_port)
                          );

                    // 6.3 epoll 对 netfd 进行读事件的监听
                    ev.data.fd = netfd;
                    ev.events = EPOLLIN;
                    epoll_ctl(epfd , EPOLL_CTL_ADD , netfd , &ev);
                }
                else
                {
                    // netfd 有已经建立的连接，有读事件就绪
                    char buff[128] = {0};

                    // 6.4 接收数据
                    ret = recv(fd , buff , sizeof(buff) , 0);

                    if(ret == 0)
                    {
                        // 6.6 表示链接已断开
                        // 只需要指定文件描述符，不需要关心事件
                        // 只需要做删除操作
                        ev.data.fd = fd;
                        // 从 epoll 监听的红黑树上删除
                        epoll_ctl(epfd , EPOLL_CTL_DEL , fd , &ev);
                        close(fd);
                        printf("文件描述符：%d 已关闭！\n" , fd);
                        continue;
                    }
                    // 6.5 连接正常的情况
                    printf("客户端：%s \n接收了 %d 字节数据 \n" , buff , ret);
                    // 服务器回显客户端信息
                    ret = send(fd , buff , strlen(buff) , 0);
                    printf("发送了 %d 字节数据 \n" , ret);
                }
            }
        }
    }

    close(epfd);
    close(listenfd);

    return 0;
}


